package sli.sli2.securedobject.service;

import java.io.BufferedReader;
import java.io.PrintWriter;

import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import org.bouncycastle.util.encoders.Base64;

import sli.sli2.channels.AESChannel;
import sli.sli2.channels.Channel;
import sli.sli2.channels.RSAChannel;
import sli.sli2.channels.TCPChannel;

/**
 * Handles the authentication with the Permission-Check-Provider
 * @author Group 8, Khassraf Roman, Mueller Robin, Zischka Stefan
 *
 */
public class AuthenticationService {
	
	private int securedObjectID;
	
	private BufferedReader in;
	private PrintWriter out;
	
	private PrivateKey myPrivateKey;
	private PublicKey pcpPublicKey;

	
	public AuthenticationService(int securedObjectID, BufferedReader in, PrintWriter out, PrivateKey myPrivateKey, PublicKey pcpPublicKey) {
		this.in = in;
		this.out = out;
		
		this.myPrivateKey = myPrivateKey;
		this.pcpPublicKey = pcpPublicKey;
		
		this.securedObjectID = securedObjectID;
	}
	
	/**
	 * Authenticates the Secured object with the Permission-Check-Provider
	 * @return a secured AES encrypted Channel to communicate with the Permission-Check-Provider
	 * @throws Exception - if something goes wrong during the authentication
	 */
	public Channel authenticate() throws Exception{
		
		TCPChannel tcpChannel = new TCPChannel(in, out);
		
		//Create RSA channel for first two authentication messages
		RSAChannel rsaChannel = null;
		try {
			rsaChannel = new RSAChannel(tcpChannel, myPrivateKey, pcpPublicKey);
		} catch (Exception e2) {
			System.out.println("Error at creating RSA channel for authentication");
			throw e2;
		}
		
		//first Message to send
		byte[] securedObjectChallengeByte = createSecureRandomNumber(32);
		String securedObjectChallenge = new String(securedObjectChallengeByte);
		String securedObjectChallengeBase64 = encodeBase64(securedObjectChallengeByte);
		
		String firstMessage = "!login "+securedObjectChallengeBase64+" "+securedObjectID;
		
		//send first message
		try {
			rsaChannel.send(firstMessage.getBytes());		
		} catch (Exception e2) {
			System.out.println("Error at sending first authentication message");
			throw e2;
		}
		
		
		//get second message from Permission-Check-Provider
		String secondMessage = null;
		try {
			secondMessage = rsaChannel.receive();		
		} catch (Exception e2) {
			System.out.println("Error at receiving second authentication message");
			throw e2;
		}
		
		//Get information from second message
		String[] split= secondMessage.split(" ");
		
		String securedObjectChallengeReturned = decodeBase64(split[1]);

			
		if(!securedObjectChallenge.equals(securedObjectChallengeReturned)){
			System.out.println("Error: secured object challenge of first and second message are not equal. Authentication failed.");
			throw new Exception();
		}
		
		String pcpChallengeBase64 = split[2];	
		byte[] secretKey = Base64.decode(split[3]);
		byte[] ivParameter = Base64.decode(split[4]);
		
		
		//Create AES channel for further secure communication with the Permission-Check-Provider
		AESChannel aesChannel = null;				
		try {
			aesChannel = new AESChannel(tcpChannel, secretKey, ivParameter);
		} catch (Exception e2) {
			System.out.println("Error at creating AESChannel.");
			throw e2;
		}
		
		//Third message
		String thirdMessage = pcpChallengeBase64;
		
		
		//Send third message
		try {
			aesChannel.send(thirdMessage.getBytes());
		} catch (Exception e2) {
			System.out.println("Error at sending third authentication message.");
			throw e2;
		}

		
		
		return aesChannel;
	}
	
	/**
	 * Encodes a byte array into Base64 format
	 * @param message - the message to encode
	 * @return the encoded message
	 */
	private String encodeBase64(byte[] message){
		
		byte[] messageByte = message;
		byte[] base64MessageByte = Base64.encode(messageByte);
		
		String base64Message = new String(base64MessageByte);
		
		return base64Message;
		
	}
	
	/**
	 * Decodes a string from Base64 format
	 * @param base64Message - the message to decode
	 * @return the decoded message
	 */
	private String decodeBase64(String base64Message){
		
		byte[] encryptedMessageByte = Base64.decode(base64Message.getBytes());
		String encryptedMessage = new String(encryptedMessageByte);
		
		return encryptedMessage;
	}
	
	/**
	 * Generates a secure random number
	 * @param byteCount - the byte count of the random number (e.g. 32 byte random number)
	 * @return the secure <byteCount> random number
	 */
	private byte[] createSecureRandomNumber(int byteCount){
		
		SecureRandom secureRandom = new SecureRandom();
		final byte[] number = new byte[byteCount];
		secureRandom.nextBytes(number);
		
		return number;
	}
	
}
